﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="KinectTab.xaml.cs" company="arvystate.net">
//   arvystate.net
// </copyright>
// <summary>
//   Interaction logic for KinectTab.xaml
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace PresentationAid.Tabs
{
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media.Imaging;

    using Elysium;

    using Kinect.Toolbox;

    using Microsoft.Kinect;

    using NLog;

    using PresentationAid.Core;
    using PresentationAid.Lib;

    /// <summary>
    /// Interaction logic for KinectTab
    /// </summary>
    public partial class KinectTab
    {
        #region Static Fields

        /// <summary>
        /// The logger.
        /// </summary>
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

        #endregion

        #region Fields

        /// <summary>
        /// The _color manager.
        /// </summary>
        private readonly ColorStreamManager colorManager;

        /// <summary>
        /// The _depth manager.
        /// </summary>
        private readonly DepthStreamManager depthManager;

        /// <summary>
        /// The _is depth view.
        /// </summary>
        private bool isDepthView;

        /// <summary>
        /// The _is loading.
        /// </summary>
        private bool isLoading;

        /// <summary>
        /// The _is seated.
        /// </summary>
        private bool isSeated;

        /// <summary>
        /// The _is skeleton.
        /// </summary>
        private bool isSkeleton;

        /// <summary>
        /// The _skeleton display manager.
        /// </summary>
        private MetroSkeletonDisplayManager skeletonDisplayManager;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="KinectTab"/> class.
        /// </summary>
        public KinectTab()
        {
            Logger.Trace("Init: KinectTab");

            this.colorManager = new ColorStreamManager();
            this.depthManager = new DepthStreamManager();

            this.InitializeComponent();

            // Setup Kinect events ot make sure application does not hang
            KinectSensorManager.Instance.KinectSensorActiveChanged += this.OnKinectSensorActiveChanged;
            KinectSensorManager.Instance.KinectSensorActiveStarted += this.OnKinectSensorActiveStarted;
            KinectSensorManager.Instance.KinectSensorActiveClosed += this.OnKinectSensorActiveClosed;

            KinectSensorManager.Instance.ColorFrameReady += this.OnColorFrameReady;
            KinectSensorManager.Instance.DepthFrameReady += this.OnDepthFrameReady;
            KinectSensorManager.Instance.SkeletonFrameReady += this.OnSkeletonFrameReady;

            KinectSensor.KinectSensors.StatusChanged += this.OnKinectSensorsStatusChanged;

            this.LoadKinectSensors();

            this.KinectImage.DataContext = this.colorManager;
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// The create combo box item.
        /// </summary>
        /// <param name="imageUri">
        /// The image uri.
        /// </param>
        /// <param name="size">
        /// The size.
        /// </param>
        /// <param name="mainTitle">
        /// The main title.
        /// </param>
        /// <param name="subTitle">
        /// The sub title.
        /// </param>
        /// <returns>
        /// The <see cref="ComboBoxItem"/>.
        /// </returns>
        public ComboBoxItem CreateComboBoxItem(string imageUri, double size, string mainTitle, string subTitle)
        {
            // Create fancy combobox items for kinect sensors
            ComboBoxItem item = new ComboBoxItem { Content = new Grid() };

            Grid grid = (Grid)item.Content;

            grid.ColumnDefinitions.Add(new ColumnDefinition());
            grid.ColumnDefinitions.Add(new ColumnDefinition());
            grid.ColumnDefinitions[0].Width = new GridLength(50.0);

            grid.RowDefinitions.Add(new RowDefinition());

            if (!string.IsNullOrEmpty(subTitle))
            {
                grid.RowDefinitions.Add(new RowDefinition());
            }

            Image image = new Image
                              {
                                  Source =
                                      new BitmapImage(
                                      new Uri(
                                      "/PresentationAid;component/" + imageUri, UriKind.RelativeOrAbsolute)), 
                                  Width = size, 
                                  Margin = new Thickness(0, 0, 5, 0), 
                                  VerticalAlignment = VerticalAlignment.Center, 
                                  HorizontalAlignment = HorizontalAlignment.Center
                              };

            if (!string.IsNullOrEmpty(subTitle))
            {
                Grid.SetRowSpan(image, 2);
            }

            grid.Children.Add(image);

            TextBlock statusBlock = new TextBlock { Text = mainTitle };

            Grid.SetColumn(statusBlock, 1);

            grid.Children.Add(statusBlock);

            if (!string.IsNullOrEmpty(subTitle))
            {
                TextBlock idBlock = new TextBlock { Text = subTitle, FontSize = 9 };

                Grid.SetColumn(idBlock, 1);
                Grid.SetRow(idBlock, 1);

                grid.Children.Add(idBlock);
            }
            else
            {
                statusBlock.VerticalAlignment = VerticalAlignment.Center;
            }

            return item;
        }

        #endregion

        #region Methods

        /// <summary>
        /// The depth view toggle changed.
        /// </summary>
        private void DepthViewToggleChanged()
        {
            Logger.Debug("Method: DepthViewToggleChanged - IsDepthView: {0}", this.DepthViewToggle.IsChecked);

            this.isDepthView = this.DepthViewToggle.IsChecked;

            if (this.isDepthView)
            {
                this.KinectImage.DataContext = this.depthManager;
            }
            else
            {
                this.KinectImage.DataContext = this.colorManager;
            }
        }

        /// <summary>
        /// The depth view toggle checked.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void DepthViewToggleChecked(object sender, RoutedEventArgs e)
        {
            this.DepthViewToggleChanged();
        }

        /// <summary>
        /// The depth view toggle unchecked.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void DepthViewToggleUnchecked(object sender, RoutedEventArgs e)
        {
            this.DepthViewToggleChanged();
        }

        /// <summary>
        /// Elevation control is changed, update Elevation Angle
        /// </summary>
        /// <param name="sender">
        /// Sender of the elevation control elevation changed event
        /// </param>
        /// <param name="eventArgs">
        /// Event arguments
        /// </param>
        private void ElevationControlOnElevationChanged(object sender, EventArgs eventArgs)
        {
            if (KinectSensorManager.Instance.IsRunning)
            {
                KinectSensorManager.Instance.ElevationAngle = this.ElevationControl.ElevationAngle;
            }
        }

        /// <summary>
        /// Initializes selected Kinect sensor
        /// </summary>
        /// <param name="sender">
        /// Sender of the kinect sensor list selection changed event
        /// </param>
        /// <param name="e">
        /// Event arguments
        /// </param>
        private void KinectSensorListSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (this.isLoading)
            {
                return;
            }

            Logger.Debug(
                "Event: KinectSensorListSelectionChanged - SelectedIndex: {0}", this.KinectSensorList.SelectedIndex);

            if (KinectSensor.KinectSensors.Count > 0)
            {
                KinectSensorManager.Instance.InitializeSensor(
                    KinectSensorManager.Instance.IsInitialized && KinectSensorManager.Instance.IsRunning
                        ? KinectSensor.KinectSensors[this.KinectSensorList.SelectedIndex]
                        : KinectSensor.KinectSensors[this.KinectSensorList.SelectedIndex - 1]);
                KinectSensorManager.Instance.StartKinect();
            }
        }

        /// <summary>
        /// The load kinect sensors.
        /// </summary>
        private void LoadKinectSensors()
        {
            Logger.Debug("Method: LoadKinectSensors - SensorCount: {0}", KinectSensor.KinectSensors.Count);

            // Lock the sensor, so changing index does not fire SelectionChanged event
            this.isLoading = true;

            // Separate logic to load Kinect Sensors
            this.KinectSensorList.Items.Clear();

            if (KinectSensor.KinectSensors.Count == 0)
            {
                this.KinectSensorList.IsEnabled = false;

                ComboBoxItem item = this.CreateComboBoxItem(
                    "Resources/Images/Icons/no-icon-disabled.png", 24, "Microsoft Kinect™ not found", string.Empty);

                this.KinectSensorList.Items.Add(item);

                this.KinectSensorList.SelectedIndex = 0;
            }
            else
            {
                this.KinectSensorList.IsEnabled = true;

                // Check if there is no initialized Kinect sensor, we add a dummy item to combobox
                if ((!KinectSensorManager.Instance.IsInitialized) || (!KinectSensorManager.Instance.IsRunning))
                {
                    Logger.Debug("MethodDetail: LoadKinectSensors - DummyItemCreate");

                    ComboBoxItem item = this.CreateComboBoxItem(
                        "Resources/Images/Icons/no-icon-disabled.png", 24, "- Choose device -", string.Empty);

                    this.KinectSensorList.Items.Add(item);

                    this.KinectSensorList.SelectedIndex = 0;
                }

                int index = 0;

                // Loop through all the Kinects attached to this PC and display them in combo box
                foreach (KinectSensor kinect in KinectSensor.KinectSensors)
                {
                    // Create fancy combobox items for kinect sensors
                    string kinectTitle = (index + 1) + ": " + kinect.Status;

                    string kinectImage = "/Resources/Images/Icons/xbox360-icon.png";

                    if (kinect.IsWindowsKinect())
                    {
                        kinectImage = "/Resources/Images/Icons/windows-icon.png";
                    }

                    if (kinect.IsRunning && (!KinectSensorManager.Instance.IsInitialized))
                    {
                        kinectTitle = (index + 1) + ": Running";
                    }

                    string kinectId = "[" + kinect.UniqueKinectId + "]";

                    if (kinect.Status != KinectStatus.Connected)
                    {
                        kinectImage = kinect.IsWindowsKinect()
                                          ? "/Resources/Images/Icons/windows-icon-disabled.png"
                                          : "/Resources/Images/Icons/xbox360-icon-disabled.png";
                    }

                    ComboBoxItem item = this.CreateComboBoxItem(kinectImage, 24, kinectTitle, kinectId);

                    this.KinectSensorList.Items.Add(item);

                    if (KinectSensorManager.Instance.KinectSensor == kinect)
                    {
                        this.KinectSensorList.SelectedIndex = index;
                    }

                    Logger.Trace(
                        "Method: LoadKinectSensors - TestedSensor, SensorNumber: {0}, Status: {1}, IsWindows: {2}, IsRunning: {3}", 
                        index, 
                        kinect.Status.ToString(), 
                        kinect.IsWindowsKinect(), 
                        kinect.IsRunning);

                    index++;
                }
            }

            this.isLoading = false;
        }

        /// <summary>
        /// The on color frame ready.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void OnColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
        {
            using (ColorImageFrame frame = e.OpenColorImageFrame())
            {
                if (frame == null)
                {
                    return;
                }

                if (!this.isDepthView)
                {
                    this.colorManager.Update(frame);
                }
            }
        }

        /// <summary>
        /// The on depth frame ready.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void OnDepthFrameReady(object sender, DepthImageFrameReadyEventArgs e)
        {
            using (DepthImageFrame frame = e.OpenDepthImageFrame())
            {
                if (frame == null)
                {
                    return;
                }

                if (this.isDepthView)
                {
                    this.depthManager.Update(frame);
                }
            }
        }

        /// <summary>
        /// The on kinect sensor active changed.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="args">
        /// The args.
        /// </param>
        private void OnKinectSensorActiveChanged(object sender, KinectSensorEventArgs args)
        {
            this.skeletonDisplayManager = new MetroSkeletonDisplayManager(
                args.Sensor, this.SkeletonCanvas, AccentBrushes.Blue, AccentBrushes.Magenta);
        }

        /// <summary>
        /// The on kinect sensor active closed.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="args">
        /// The args.
        /// </param>
        private void OnKinectSensorActiveClosed(object sender, KinectSensorEventArgs args)
        {
            this.ElevationControl.IsEnabled = false;

            this.LoadKinectSensors();
        }

        /// <summary>
        /// The on kinect sensor active started.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="args">
        /// The args.
        /// </param>
        private void OnKinectSensorActiveStarted(object sender, KinectSensorEventArgs args)
        {
            this.ElevationControl.IsEnabled = true;
            this.ElevationControl.ElevationAngle = KinectSensorManager.Instance.ElevationAngle;

            this.LoadKinectSensors();
        }

        /// <summary>
        /// The on kinect sensors status changed.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void OnKinectSensorsStatusChanged(object sender, StatusChangedEventArgs e)
        {
            this.LoadKinectSensors();
        }

        /// <summary>
        /// Function receives kinect skeleton frame, which contains data of currently tracked
        /// </summary>
        /// <param name="sender">
        /// Sender of on skeleton frame ready event
        /// </param>
        /// <param name="e">
        /// Event arguments
        /// </param>
        private void OnSkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            using (SkeletonFrame frame = e.OpenSkeletonFrame())
            {
                if (frame == null)
                {
                    return;
                }

                Skeleton[] skeletons = null;

                frame.GetSkeletons(ref skeletons);

                if ((Math.Abs(this.SkeletonCanvas.ActualWidth - 0.0) < double.Epsilon)
                    || (Math.Abs(this.SkeletonCanvas.ActualHeight - 0.0) < double.Epsilon))
                {
                    this.SkeletonCanvas.Width = this.KinectImage.ActualWidth;
                    this.SkeletonCanvas.Height = this.KinectImage.ActualHeight;
                }

                this.skeletonDisplayManager.Draw(skeletons, this.isSeated);
            }
        }

        /// <summary>
        /// The seated toggle changed.
        /// </summary>
        private void SeatedToggleChanged()
        {
            Logger.Debug("Method: SeatedToggleChanged - IsSeated: {0}", this.SeatedToggle.IsChecked);

            this.isSeated = this.SeatedToggle.IsChecked;
        }

        /// <summary>
        /// The seated toggle checked.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void SeatedToggleChecked(object sender, RoutedEventArgs e)
        {
            this.SeatedToggleChanged();
        }

        /// <summary>
        /// The seated toggle unchecked.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void SeatedToggleUnchecked(object sender, RoutedEventArgs e)
        {
            this.SeatedToggleChanged();
        }

        /// <summary>
        /// The skeleton toggle changed.
        /// </summary>
        private void SkeletonToggleChanged()
        {
            Logger.Debug("Method: SkeletonToggleChanged - IsSkeleton: {0}", this.SkeletonToggle.IsChecked);

            this.isSkeleton = this.SkeletonToggle.IsChecked;

            this.SkeletonCanvas.Visibility = this.isSkeleton ? Visibility.Visible : Visibility.Hidden;
        }

        /// <summary>
        /// The skeleton toggle checked.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void SkeletonToggleChecked(object sender, RoutedEventArgs e)
        {
            this.SkeletonToggleChanged();
        }

        /// <summary>
        /// The skeleton toggle unchecked.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void SkeletonToggleUnchecked(object sender, RoutedEventArgs e)
        {
            this.SkeletonToggleChanged();
        }

        /// <summary>
        /// The user control size changed.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void UserControlSizeChanged(object sender, SizeChangedEventArgs e)
        {
            this.SkeletonCanvas.Width = this.KinectImage.ActualWidth;
            this.SkeletonCanvas.Height = this.KinectImage.ActualHeight;
        }

        #endregion
    }
}